<!DOCTYPE HTML>
<html>
	<head>
		<title>Velocity.js Tests</title>
		<link rel="stylesheet" href="qunit-1.14.0.css" type="text/css" media="screen" />
		<script type="text/javascript" src="qunit-1.14.0.js"></script>
		<script type="text/javascript" src="jquery-1.4.3.js"></script>
		<script type="text/javascript" src="when.js"></script>
		<script type="text/javascript" no="zepto.js"></script>
		<script type="text/javascript" src="../velocity.js"></script>
		<script type="text/javascript" src="../velocity.ui.js"></script>
		<style>
			#details {
				margin: 0 auto;
				margin-bottom: 1em;

				width: 800px;
				padding: 0;

				line-height: 1.35em;
				list-style: none;
			}
		</style>
	</head>
	<body>
		<ul id="details">	
			<li>
				Unit tests: <i><b>current document</b>.</i>
			</li>
			<li>
				Performance tests: <i>See <b>Performance</b> pane in <a href="../../index.html#performance">docs</a>.</i>
			</li>
			<li>
				Property support tests: <i>Run the Property Support pane's <a href="../../index.html#propertiesTest">auto-cycler</a>.</i>
			</li>
			<li>
				Visual tests: <i>See <a href="../../demo.html"><b>demo</b></a>. Read demo's source for intended behavior.</i>
			</li>
		</ul>
		<div id="qunit"></div>
		<div id="qunit-stage"></div>
		<script>

		// Needed tests:
		// - new stop behvaior
		// - e/p/o shorthands

		/*********************
		   Helper Functions
		*********************/

		/* IE detection: https://gist.github.com/julianshapiro/9098609 */
		var IE = (function() { 
		    if (document.documentMode) {
		        return document.documentMode;
		    } else {
		        for (var i = 7; i > 0; i--) {
		            var div = document.createElement("div");

		            div.innerHTML = "<!--[if IE " + i + "]><span></span><![endif]-->";

		            if (div.getElementsByTagName("span").length) {
		                div = null;

		                return i;
		            }

		            div = null;
		        }
		    }

		    return undefined;
		})();

    	var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
    	var isAndroid = /Android/i.test(navigator.userAgent);

    	function applyStartValues (element, startValues) {
    		Velocity.Utilities.each(startValues, function(property, startValue) {
    			element.style[property] = startValue;
    		});
    	}

		/*********************
		   IE<=7 Reversion
		*********************/

  		if (IE <= 7 && $.fn.velocity) {
  			alert("Velocity wasn't reverted to jQuery's $.animate() for IE<=7.");
  		}

  		/*******************
  		    jQuery Shim
  		*******************/

  		var $ = (window.jQuery || window.Zepto);

  		Data = function(element) { 
  			return Velocity.Utilities.data(element, "velocity");
  		}

  		if ($ && $.Velocity) {
  			window.Velocity = $.Velocity;
  		}

		/*******************
		   Setup: Targets
		*******************/

		var $qunitStage = document.getElementById("qunit-stage");

		var targetsFragment = document.createDocumentFragment(),
			targetsIndex = 0,
			$targets,
			$targetSet,
			defaultStyles = {
				opacity: 1,
				width: 1,
				height: 1,
				marginBottom: 1,
				colorGreen: 200
			};

		for (var i = 0; i < 100; i++) {
			var div = document.createElement("div");

			div.className = "target";
			div.style.opacity = defaultStyles.opacity;
			div.style.color = "rgb(125, " + defaultStyles.colorGreen + ", 125)";
			div.style.width = defaultStyles.width + "px";
			div.style.height = defaultStyles.height + "px";
			div.style.marginBottom = defaultStyles.marginBottom + "px";
			div.style.textShadow = "0px 0px " + defaultStyles.textShadowBlur + "px red";

			targetsFragment.appendChild(div);
			div = null;
		}

		$qunitStage.appendChild(targetsFragment);
		$targets = $qunitStage.querySelectorAll(".target");

		function getTarget () {
			var newTarget = $targets[targetsIndex++];

			return newTarget;
		}

		/********************
		   Setup: Settings
		********************/

		var	pluginName = "velocity",
			defaultProperties = {
				opacity: defaultStyles.opacity / 2,
				width: defaultStyles.width * 2,
				height: defaultStyles.height * 2,
				colorGreen: defaultStyles.colorGreen / 2
			},
			defaultOptions = { 
				queue: "",
			    duration: 300,
			    easing: "swing",
			    begin: null,
			    complete: null,
				progress: null,
			    display: null,
			    loop: false,
			    delay: false,
			    mobileHA: true,
			    _cacheValues: true
			},
			asyncCheckDuration = defaultOptions.duration / 2,
			completeCheckDuration = defaultOptions.duration * 2

		Velocity.defaults = defaultOptions;

		/****************
		    Arguments
		****************/

		QUnit.test("Arguments", function() {
			var testComplete = function() { /* Do nothing */ },
				testDuration = 1000,
				testEasing = "easeInSine",
				testOptions = {
					queue: defaultOptions.queue,
					duration: testDuration,
					easing: testEasing,
					begin: null,
					complete: testComplete,
					progress: null,
					display: "block",
					loop: false,
					delay: false,
					mobileHA: false,
			    	_cacheValues: defaultOptions._cacheValues
				};

			/**********************
			   Invalid Arguments
			**********************/

			var $target1 = getTarget();
			/* No arguments: Ensure an error isn't thrown and that the $targeted elements are returned to the chain. */
			Velocity();
			Velocity($target1);
			Velocity($target1, {});
			Velocity($target1, {}, testDuration);
			/* Invalid arguments: Ensure an error isn't thrown. */
			Velocity($target1, "fakeArg1", "fakeArg2");

			/****************
			   Overloading
			****************/

			var $target3 = getTarget();
			Velocity($target3, defaultProperties, testDuration);
			equal(Data($target3, pluginName).opts.duration, testDuration, "Overload variation #2.");

			var $target4 = getTarget();
			Velocity($target4, defaultProperties, testEasing);
			equal(Data($target4, pluginName).opts.easing, testEasing, "Overload variation #3.");

			var $target5 = getTarget();
			Velocity($target5, defaultProperties, testComplete);
			equal(Data($target5, pluginName).opts.complete, testComplete, "Overload variation #4.");

			var $target6 = getTarget();
			Velocity($target6, defaultProperties, testDuration, [ 0.42, 0, 0.58, 1 ]);
			equal(Data($target6, pluginName).opts.duration, testDuration, "Overload variation #5a.");
			equal(Data($target6, pluginName).opts.easing(0.2), 0.0816598562658975, "Overload variation #5b.");

			var $target7 = getTarget();
			Velocity($target7, defaultProperties, testDuration, testComplete);
			equal(Data($target7, pluginName).opts.duration, testDuration, "Overload variation #6a.");
			equal(Data($target7, pluginName).opts.complete, testComplete, "Overload variation #6b.");

			var $target8 = getTarget();
			Velocity($target8, defaultProperties, testDuration, testEasing, testComplete);
			equal(Data($target8, pluginName).opts.duration, testDuration, "Overload variation #7a.");
			equal(Data($target8, pluginName).opts.easing, testEasing, "Overload variation #7b.");
			equal(Data($target8, pluginName).opts.complete, testComplete, "Overload variation #7c.");

			var $target9 = getTarget();
			Velocity($target9, defaultProperties, testOptions);
			deepEqual(Data($target9, pluginName).opts, testOptions, "Overload variation #8: options object.");

			var $target10 = getTarget();
			Velocity({ elements: $target10, properties: defaultProperties, options: testOptions });
			deepEqual(Data($target10, pluginName).opts, testOptions, "Overload variation #9: single object w/ map.");

			var $target11 = getTarget();
			Velocity({ elements: $target11, properties: "fadeOut", options: testOptions });
			strictEqual(Data($target11, pluginName).tweensContainer.opacity.endValue, 0, "Overload variation #9: single object w/ redirect.");

			var $target12 = getTarget();
			Velocity($target12, { opacity: [ 0.75, "spring", 0.25 ]}, testDuration);
			equal(Data($target12, pluginName).tweensContainer.opacity.startValue, 0.25, "Overload variation #10a.");
			equal(Data($target12, pluginName).tweensContainer.opacity.easing, "spring", "Overload variation #10b.");
			equal(Data($target12, pluginName).tweensContainer.opacity.endValue, 0.75, "Overload variation #10c.");

			var $target13 = getTarget();
			Velocity($target13, { opacity: [ 0.75, 0.25 ]}, testDuration);
			equal(Data($target13, pluginName).tweensContainer.opacity.startValue, 0.25, "Overload variation #11a.");
			equal(Data($target13, pluginName).tweensContainer.opacity.endValue, 0.75, "Overload variation #11b.");

			var $target14 = getTarget();
			Velocity($target14, { opacity: [ 0.75, "spring" ]}, testDuration);
			equal(Data($target14, pluginName).tweensContainer.opacity.endValue, 0.75, "Overload variation #12a.");
			equal(Data($target14, pluginName).tweensContainer.opacity.easing, "spring", "Overload variation #12b.");

			var $target15 = getTarget();
			Velocity($target15, defaultProperties, "fast", testEasing);
			equal(Data($target15, pluginName).opts.duration, 200, "Overload variation #13a.");

			var $target16 = getTarget();
			Velocity($target16, defaultProperties, "normal");
			equal(Data($target16, pluginName).opts.duration, 400, "Overload variation #13b.");

			if ($) {
				var $target17 = getTarget();
				$($target17).velocity(defaultProperties, testOptions);
				deepEqual(Data($target17, pluginName).opts, testOptions, "$.fn.: Utility function variation #1: options object.");

				var $target18 = getTarget();
				$($target18).velocity({ properties: defaultProperties, options: testOptions });
				deepEqual(Data($target18, pluginName).opts, testOptions, "$.fn.: Utility function variation #2: single object.");

				var $target19 = getTarget();
				$($target19).velocity(defaultProperties, testDuration, testEasing, testComplete);
				equal(Data($target19, pluginName).opts.duration, testDuration, "$.fn.: Utility function variation #2a.");
				equal(Data($target19, pluginName).opts.easing, testEasing, "$.fn.: Utility function variation #2b.");
				equal(Data($target19, pluginName).opts.complete, testComplete, "$.fn.: Utility function variation #2c.");

				var $target20 = getTarget();
				deepEqual($($target20), $($target20).velocity(defaultProperties, testDuration, testEasing, testComplete).velocity(defaultProperties, testDuration, testEasing, testComplete), "$.fn.: Elements passed back to the call stack.");
			}
		});

		/******************
		    CSS Object
		******************/

		/* Note: We don't bother checking all of the GET/SET-related CSS object's functions, as most are repeatedly tested indirectly via the other tests. */
		QUnit.test("CSS Object", function() {
			var CSS = Velocity.CSS;

			var testHookRoot = "boxShadow",
				testHookRootValue = IE >=9 ? "1px 2px 3px 4px black" : "black 1px 2px 3px 4px",
				testHook = "boxShadowY",
				testHookValueExtracted = "2px",
				testHookValueInject = "10px",
				testHookRootValueInjected = "1px 10px 3px 4px"

			/* Hooks manipulation. */
			equal(CSS.Hooks.getRoot(testHook), testHookRoot, "Hooks.getRoot() returned root.");

			/* Hooks have no effect if they're unsupported (which is the case for our hooks on <=IE8), thus we just ensure that errors aren't thrown. */
			if (IE <=8) {
				CSS.Hooks.extractValue(testHook, testHookRootValue);
				CSS.Hooks.injectValue(testHook, testHookValueInject, testHookRootValue)
			} else {
				equal(CSS.Hooks.extractValue(testHook, testHookRootValue), testHookValueExtracted, "Hooks.extractValue() returned value #1.");
				/* Check for a match anywhere in the string since browser differ in where they inject the color value. */
				equal(CSS.Hooks.injectValue(testHook, testHookValueInject, testHookRootValue).indexOf(testHookRootValueInjected) != -1, true, "Hooks.extractValue() returned value #2.");
			}

			/* Varied start color formats. Ensure that all variations of red output contain the same color copmonents when Normalized: "255 0 0". */
			var redVariations = [
				"rgb(255, 0, 0)",
				"rgba(255,0,0,0.5)",
				"#ff0000",
				"red"
			];

			Velocity.Utilities.each(redVariations, function(i, redVariation) {
				equal(/255 0 0/.test(CSS.Normalizations.registered["color"]("extract", null, redVariation)), true, "Normalizations.extractValue() returned red color variation #" + i + ".");
			});

			var testPropertyFake = "fakeProperty";

			/* Property name functions. */
			equal(CSS.Names.prefixCheck(testPropertyFake)[0], testPropertyFake, "Names.prefixCheck() returned unmatched property untouched.");
			equal(CSS.Names.prefixCheck(testPropertyFake)[1], false, "Names.prefixCheck() indicated that unmatched property waws unmatched.");
			equal(CSS.Values.isCSSNullValue("rgba(0,0,0,0)"), true, "Values.isCSSNullValue() matched null value #1.");
			equal(CSS.Values.isCSSNullValue("none"), true, "Values.isCSSNullValue() matched null value #2.");
			equal(CSS.Values.isCSSNullValue(10), false, "Values.isCSSNullValue() didn't match non-null value.");

			var testUnitProperty1 = "rotateZ",
				testUnitPropertyUnit1 = "deg",
				testUnitProperty2 = "width",
				testUnitPropertyUnit2 = "px",
				testElementType1 = document.createElement("div"),
				testElementTypeDisplay1 = "block",
				testElementType2 = document.createElement("span"),
				testElementTypeDisplay2 = "inline";

			/* CSS value functions. */
			equal(CSS.Values.getUnitType(testUnitProperty1), testUnitPropertyUnit1, "Unit type #1 was retrieved.");
			equal(CSS.Values.getUnitType(testUnitProperty2), testUnitPropertyUnit2, "Unit type #2 was retrieved.");

			/* Class addition/removal. */
			var $target1 = getTarget();
			$target1.className = "";
			CSS.Values.addClass($target1, "one");
			equal($target1.className, "one", "First class was added.");
			CSS.Values.addClass($target1, "two");
			equal($target1.className, "one two", "Second class was added.");

			CSS.Values.removeClass($target1, "two");
			equal($target1.className.replace(/^\s+|\s+$/g, ""), "one", "Second class was removed.");
			CSS.Values.removeClass($target1, "one");
			equal($target1.className.replace(/^\s+|\s+$/g, ""), "", "First class was removed.");
		});

		/****************************
		   Start Value Calculation
		****************************/

		QUnit.test("Start Value Calculation", function() {
			var testStartValues = { paddingLeft: "10px", height: "100px", paddingRight: "50%", marginLeft: "100px", marginBottom: "33%", marginTop: "100px", lineHeight: "30px", wordSpacing: "40px", backgroundColorRed: "123" };

			/* Properties not previously defined on the element. */
			var $target1 = getTarget();
			Velocity($target1, testStartValues)
			equal(Data($target1, pluginName).tweensContainer.paddingLeft.startValue, 0, "Undefined standard start value was calculated.");
			equal(Data($target1, pluginName).tweensContainer.backgroundColorRed.startValue, 255, "Undefined start value hook was calculated.");

			/* Properties previously defined on the element. */
			var $target2 = getTarget();
			Velocity($target2, defaultProperties)
			equal(Data($target2, pluginName).tweensContainer.width.startValue, parseFloat(defaultStyles.width), "Defined start value #1 was calculated.");
			equal(Data($target2, pluginName).tweensContainer.opacity.startValue, parseFloat(defaultStyles.opacity), "Defined start value #2 was calculated.");
			equal(Data($target2, pluginName).tweensContainer.colorGreen.startValue, parseFloat(defaultStyles.colorGreen), "Defined hooked start value was calculated.");

			/* Properties that shouldn't cause start values to be unit-converted. */
			var testPropertiesEndNoConvert = { paddingLeft: "20px", height: "40px", paddingRight: "75%" };
			var $target3 = getTarget();
			applyStartValues($target3, testStartValues);
			Velocity($target3, testPropertiesEndNoConvert);
			equal(Data($target3, pluginName).tweensContainer.paddingLeft.startValue, parseFloat(testStartValues.paddingLeft), "Start value #1 wasn't unit converted.");
			equal(Data($target3, pluginName).tweensContainer.height.startValue, parseFloat(testStartValues.height), "Start value #2 wasn't unit converted.");
			equal(Data($target3, pluginName).tweensContainer.paddingRight.startValue, parseFloat(testStartValues.paddingRight), "Start value #3 wasn't unit converted.");

			/* Properties that should cause start values to be unit-converted. */
			var testPropertiesEndConvert = { paddingLeft: "20%", height: "40%", lineHeight: "0.5em", wordSpacing: "2rem", marginLeft: "10vw", marginTop: "5vh", marginBottom: "100px" };
				parentWidth = $qunitStage.clientWidth,
				parentHeight = $qunitStage.clientHeight,
				parentFontSize = Velocity.CSS.getPropertyValue($qunitStage, "fontSize"),
				remSize = parseFloat(Velocity.CSS.getPropertyValue(document.body, "fontSize"));

			var $target4 = getTarget();
			applyStartValues($target4, testStartValues);
			Velocity($target4, testPropertiesEndConvert);

			/* Long decimal results can be returned after unit conversion, and Velocity's code and the code here can differ in precision. So, we round floor values before comparison. */
			equal(Math.floor(Data($target4, pluginName).tweensContainer.paddingLeft.startValue), Math.floor((parseFloat(testStartValues.paddingLeft) / parentWidth) * 100), "Horizontal property converted to %.");
			equal(Math.floor(Data($target4, pluginName).tweensContainer.height.startValue), Math.floor((parseFloat(testStartValues.height) / parentHeight) * 100), "Vertical property converted to %.");
			equal(Math.floor(Data($target4, pluginName).tweensContainer.lineHeight.startValue), Math.floor(parseFloat(testStartValues.lineHeight) / parseFloat(parentFontSize)), "Property converted to em.");
			equal(Math.floor(Data($target4, pluginName).tweensContainer.wordSpacing.startValue), Math.floor(parseFloat(testStartValues.wordSpacing) / parseFloat(remSize)), "Property converted to rem.");
			equal(Math.floor(Data($target4, pluginName).tweensContainer.marginBottom.startValue), parseFloat(testStartValues.marginBottom) / 100 * parseFloat($target4.parentNode.offsetWidth), "Property converted to px.");

			if (!(IE<=8) && !isAndroid) {
				equal(Math.floor(Data($target4, pluginName).tweensContainer.marginLeft.startValue), Math.floor(parseFloat(testStartValues.marginLeft) / window.innerWidth * 100), "Horizontal property converted to vw.");
				equal(Math.floor(Data($target4, pluginName).tweensContainer.marginTop.startValue), Math.floor(parseFloat(testStartValues.marginTop) / window.innerHeight * 100), "Vertical property converted to vh.");
			}

  			/* jQuery TRBL deferring. */
			var testPropertiesTRBL = { left: "1000px" };
			var $TRBLContainer = document.createElement("div");

			$TRBLContainer.setAttribute("id", "TRBLContainer");
			$TRBLContainer.style.marginLeft = testPropertiesTRBL.left;
			$TRBLContainer.style.width = "100px";
			$TRBLContainer.style.height = "100px";
  			document.body.appendChild($TRBLContainer);

			var $target5 = getTarget();
			$target5.style.position = "absolute";
  			$TRBLContainer.appendChild($target5);
			Velocity($target5, testPropertiesTRBL);

			equal(Math.round(Data($target5, pluginName).tweensContainer.left.startValue), Math.round(parseFloat(testPropertiesTRBL.left) + parseFloat(Velocity.CSS.getPropertyValue(document.body, "marginLeft"))), "TRBL value was deferred to jQuery.");
		});

		/**************************
		   End Value Calculation
		**************************/

		QUnit.asyncTest("End Value Calculation", function() {
			/* Standard properties without operators. */
			var $target1 = getTarget();
			Velocity($target1, defaultProperties);
			setTimeout(function() {
				equal(Data($target1, pluginName).tweensContainer.width.endValue, defaultProperties.width, "Standard end value #1 was calculated.");
				equal(Data($target1, pluginName).tweensContainer.opacity.endValue, defaultProperties.opacity, "Standard end value #2 was calculated.");
			}, asyncCheckDuration);

			/* Standard properties with operators. */
			var testIncrementWidth = "5px",
				testDecrementOpacity = 0.25,
				testMultiplyMarginBottom = 4,
				testDivideHeight = 2;

			var $target2 = getTarget();
			Velocity($target2, { width: "+=" + testIncrementWidth, opacity: "-=" + testDecrementOpacity, marginBottom: "*=" + testMultiplyMarginBottom, height: "/=" + testDivideHeight });
			setTimeout(function() {

				equal(Data($target2, pluginName).tweensContainer.width.endValue, defaultStyles.width + parseFloat(testIncrementWidth), "Incremented end value was calculated.");
				equal(Data($target2, pluginName).tweensContainer.opacity.endValue, defaultStyles.opacity - testDecrementOpacity, "Decremented end value was calculated.");
				equal(Data($target2, pluginName).tweensContainer.marginBottom.endValue, defaultStyles.marginBottom * testMultiplyMarginBottom, "Multiplied end value was calculated.");
				equal(Data($target2, pluginName).tweensContainer.height.endValue, defaultStyles.height / testDivideHeight, "Divided end value was calculated.");

				start();
			}, asyncCheckDuration);
		});

		/**********************
		   End Value Setting
		**********************/

		QUnit.asyncTest("End Value Setting (Note: Browser Tab Must Have Focus Due to rAF)", function() {
			/* Transforms and the properties that are hooked by Velocity aren't supported below IE9. */
			if (!(IE < 9)) {
				var testHooks = { 
					boxShadowBlur: "10px", // "black 0px 0px 10px 0px"
					boxShadowSpread: "20px", // "black 0px 0px 0px 20px"
					textShadowBlur: "30px" // "black 0px 0px 30px"
				};

				/* Hooks. */
				var $target3 = getTarget();
				Velocity($target3, testHooks);
				setTimeout(function() {
					/* Check for a match anywhere in the string since browser differ in where they inject the color value. */
					equal(/0px 0px 10px 20px/.test(Velocity.CSS.getPropertyValue($target3, "boxShadow")), true, "Hook end value #1 was set.");
					/* textShadow isn't supported below IE10. */
					if (!IE || IE >= 10) {
						equal(/0px 0px 30px/.test(Velocity.CSS.getPropertyValue($target3, "textShadow")), true, "Hook end value #2 was set.");
					}
				}, completeCheckDuration);

				if (!(IE < 10) && !Velocity.State.isGingerbread) {
					var testTransforms = { 
							translateY: "10em", // Should stay the same
							translateX: "20px", // Should stay the same
							scaleX: "1.50", // Should remain unitless
							translateZ: "30", // Should become "10px"
							scaleY: "1.50deg" // Should be ignored entirely since it uses an invalid unit
						},
						testTransformsOutput = "matrix3d(1.5, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 20, 160, 30, 1)";

					/* Transforms. */
					var $target4 = getTarget();
					Velocity($target4, testTransforms);
					setTimeout(function() {
						/* Check for a match anywhere in the string since browser differ in where they inject the color value. */
						equal(Velocity.CSS.getPropertyValue($target4, "transform"), testTransformsOutput, "Transform end value was set.");

						/* Ensure previous transform values are reused. */
						Velocity($target4, { translateX: parseFloat(testTransforms.translateX) / 2 });
						equal(Data($target4, pluginName).tweensContainer.translateX.startValue, parseFloat(testTransforms.translateX), "Previous transform value was reused.");
					}, completeCheckDuration);
				}

				if (!Velocity.State.isGingerbread) {
					/* SVG. */
					var $svgRoot = document.createElementNS("http://www.w3.org/2000/svg", "svg"),
						$svgRect = document.createElementNS("http://www.w3.org/2000/svg", "rect"),
						svgStartValues = { x: 100, y: 10, width: 250, height: "30%" },
						svgEndValues = { x: 200, width: "50%", strokeDasharray: 10, height: "40%", rotateZ: "90deg", rotateX: "45deg" };

					$svgRoot.setAttribute("width", 1000);
					$svgRoot.setAttribute("height", 1000);

					$svgRect.setAttribute("x", svgStartValues.x);
					$svgRect.setAttribute("y", svgStartValues.y);
					$svgRect.setAttribute("width", svgStartValues.width);
					$svgRect.setAttribute("height", svgStartValues.height);

					$svgRoot.appendChild($svgRect);
					$qunitStage.appendChild($svgRoot);

					Velocity($svgRect, svgEndValues, defaultOptions);
					setTimeout(function() {
						equal(Math.round(Data($svgRect, pluginName).tweensContainer.x.startValue), svgStartValues.x, "SVG dimensional attribute #1 value was retrieved.");
						equal(Math.round(parseFloat(Velocity.CSS.getPropertyValue($svgRect, "x"))), svgEndValues.x, "SVG dimensional attribute #1 end value was set.");

						equal(Math.round(Data($svgRect, pluginName).tweensContainer.width.startValue), parseFloat(svgStartValues.width)/1000 * 100, "SVG dimensional attribute #2 value was retrieved.");
						equal(Math.round(parseFloat(Velocity.CSS.getPropertyValue($svgRect, "width"))), parseFloat(svgEndValues.width)/100 * 1000, "SVG dimensional attribute #2 end value was set.");

						equal(Math.round(Data($svgRect, pluginName).tweensContainer.height.startValue), parseFloat(svgStartValues.height), "SVG dimensional attribute #3 value was retrieved.");
						equal(Math.round(parseFloat(Velocity.CSS.getPropertyValue($svgRect, "height"))), parseFloat(svgEndValues.height)/100 * 1000, "SVG dimensional attribute #3 end value was set.");

						equal(Math.round(Data($svgRect, pluginName).tweensContainer.rotateZ.startValue), 0, "SVG 2D transform value was retrieved.");
						equal(Math.round(parseFloat(Velocity.CSS.getPropertyValue($svgRect, "rotateZ"))), parseFloat(svgEndValues.rotateZ), "SVG 2D transform end value was set.");

						if (!IE) {
							equal(Math.round(Data($svgRect, pluginName).tweensContainer.rotateX.startValue), 0, "SVG 3D transform value was retrieved.");
							equal(Math.round(parseFloat(Velocity.CSS.getPropertyValue($svgRect, "rotateX"))), parseFloat(svgEndValues.rotateX), "SVG 3D transform end value was set.");
						}

						equal(Math.round(Data($svgRect, pluginName).tweensContainer.strokeDasharray.startValue), 0, "SVG CSS style value was retrieved.");
						equal(parseFloat(Velocity.CSS.getPropertyValue($svgRect, "strokeDasharray")), svgEndValues.strokeDasharray, "SVG CSS style end value was set.");
					}, completeCheckDuration);
				}
			}

			/* Standard properties. */
			var $target1 = getTarget();
			Velocity($target1, defaultProperties, { });
			setTimeout(function() {
				equal(parseFloat(Velocity.CSS.getPropertyValue($target1, "width")), defaultProperties.width, "Standard end value #1 was set.");
				equal(parseFloat(Velocity.CSS.getPropertyValue($target1, "opacity")), defaultProperties.opacity, "Standard end value #2 was set.");

				start();
			}, completeCheckDuration);
		});

		/**********************
		   End Value Caching
		**********************/

		QUnit.asyncTest("End Value Caching", function() {
			expect(4);

			var newProperties = { height: "50px", width: "250px" };

			var $target1 = getTarget();
			Velocity($target1, defaultProperties, function() {

				applyStartValues($target1, newProperties);
				/* Called after the last call is complete (stale). Ensure that the newly-set (via $.css()) properties are used. */
				Velocity($target1, defaultProperties);

				setTimeout(function() {
					equal(Data($target1, pluginName).tweensContainer.width.startValue, parseFloat(newProperties.width), "Stale end value #1 wasn't pulled.");
					equal(Data($target1, pluginName).tweensContainer.height.startValue, parseFloat(newProperties.height), "Stale end value #2 wasn't pulled.");
				}, asyncCheckDuration);
			});

			var $target2 = getTarget();
			Velocity($target2, defaultProperties);
			Velocity($target2, newProperties, function() {
				/* Chained onto a previous call (fresh). */
				equal(Data($target2, pluginName).tweensContainer.width.startValue, defaultProperties.width, "Chained end value #1 was pulled.");
				equal(Data($target2, pluginName).tweensContainer.height.startValue, defaultProperties.height, "Chained end value #2 was pulled.");

				start();
			});
		});

		/****************
		    Queueing
		****************/

		QUnit.asyncTest("Queueing", function() {
			expect(1);

			var $target1 = getTarget();
			Velocity($target1, { opacity: 0 });
			Velocity($target1, { width: 2 });

			setTimeout(function() {
				/* Ensure that the second call hasn't started yet. */
				equal(parseFloat(Velocity.CSS.getPropertyValue($target1, "width")), defaultStyles.width, "Queued calls chain.");

				start();
			}, asyncCheckDuration);
		});

		/******************
		   Option: Queue
		******************/

		QUnit.asyncTest("Option: Queue", function() {
			expect(5);

			var testQueue = "custom";

			var $target1 = getTarget();
			Velocity($target1, defaultProperties, { queue: testQueue });
			Velocity($target1, defaultProperties, { queue: testQueue });
			Velocity($target1, defaultProperties, { queue: testQueue });

			equal(Velocity.Utilities.queue($target1, testQueue).length, 3, "Custom queue was appended to.");
			equal(Data($target1, pluginName).isAnimating, false, "Custom queue didn't auto-dequeue.");

			Velocity.Utilities.dequeue($target1, testQueue);
			equal(Data($target1, pluginName).isAnimating, true, "Dequeue custom queue.");

			Velocity($target1, "stop", testQueue);
			equal(Velocity.Utilities.queue($target1, testQueue).length, 0, "Stopped custom queue.");

			var $target2 = getTarget();
			Velocity($target2, { opacity: 0 });
			Velocity($target2, { width: 10 }, { queue: false });

			setTimeout(function() {
				/* Ensure that the second call starts immediately. */
				notEqual(Velocity.CSS.getPropertyValue($target2, "width"), defaultStyles.width, "Parallel calls don't queue.");

				start();
			}, asyncCheckDuration);
		});

		/* Helpful redirect for testing custom and parallel queues. */
		// var $div2 = $("#DataBody-PropertiesDummy");
		// $.fn.velocity.defaults.duration = 1000;
		// $div2.velocity("scroll", { queue: "test" })
		// $div2.velocity({width: 100}, { queue: "test" })
		// $div2.velocity({ borderWidth: 50 }, { queue: "test" })
		// $div2.velocity({height: 20}, { queue: "test" })
		// $div2.velocity({marginLeft: 200}, { queue: "test" })
		// $div2.velocity({paddingTop: 60});
		// $div2.velocity({marginTop: 100});
		// $div2.velocity({paddingRight: 40});
		// $div2.velocity({marginTop: 0})
		// $div2.dequeue("test")

		/******************
		   Option: Delay   
		******************/

		QUnit.asyncTest("Option: Delay (Note: Browser Tab Must Have Focus Due to rAF)", function() {
			expect(2);

			var testDelay = defaultOptions.duration * 2;

			var $target = getTarget();
			Velocity($target, defaultProperties, { delay: testDelay });

			setTimeout(function() {
				equal(parseFloat(Velocity.CSS.getPropertyValue($target, "width")), defaultStyles.width, "Delayed calls don't start immediately");
			}, asyncCheckDuration);

			setTimeout(function() {
				equal(parseFloat(Velocity.CSS.getPropertyValue($target, "width")), defaultProperties.width, "Delayed calls eventually start.");

				start();
			}, completeCheckDuration + testDelay);
		});

		/********************
		   Option: Easing   
		********************/

		QUnit.asyncTest("Option: Easing", function() {
			expect(5);

			var $target1 = getTarget();
			/* Ensure that a fake easing doesn't throw an error. */
			Velocity($target1, defaultProperties, { easing: "fake" });
			equal(true, true, "Fake easing didn't throw error.");

			/* Ensure that an improperly-formmated bezier curve array doesn't throw an error. */
			var $target2 = getTarget();
			Velocity($target2, defaultProperties, { easing: [ "a", 0.5, 0.5, 0.5 ] });
			Velocity($target2, defaultProperties, { easing: [ 0.5, 0.5, 0.5 ] });
			equal(true, true, "Invalid bezier curve didn't throw error.");

			/* Ensure that a properly-formatted bezier curve array returns a bezier function. */
			var $target3 = getTarget();
			var easingBezierArray = [ 0.27, -0.65, 0.78, 0.19 ],
				easingBezierTestPercent = 0.25,
				easingBezierTestValue = /^-0\.23/;

			Velocity($target3, defaultProperties, { easing: easingBezierArray });
			setTimeout(function() {
				equal(easingBezierTestValue.test(Data($target3, pluginName).tweensContainer.width.easing(easingBezierTestPercent)), true, "Array converted into bezier function.");
			}, asyncCheckDuration);

			/* Ensure that a properly-formatted spring RK4 array returns a bezier function. */
			var $target4 = getTarget();
			var easingSpringRK4Array = [ 250, 12 ],
				easingSpringRK4TestPercent = 0.25,
				easingSpringRK4TestValue = /^1\.21/;

			Velocity($target4, defaultProperties, { easing: easingSpringRK4Array });
			setTimeout(function() {
				equal(easingSpringRK4TestValue.test(Data($target4, pluginName).tweensContainer.width.easing(easingSpringRK4TestPercent)), true, "Array converted into springRK4 function.");
			}, asyncCheckDuration);

			/* Ensure that a properly-formatted step easing array returns a step function. */
			var $target5 = getTarget();
			var easingStepArray = [ 4 ],
				easingStepTestPercent = 0.35,
				easingStepTestValue = /^0\.25/;

			Velocity($target5, defaultProperties, { easing: easingStepArray });
			setTimeout(function() {
				equal(easingStepTestValue.test(Data($target5, pluginName).tweensContainer.width.easing(easingStepTestPercent)), true, "Array converted into Step function.");

				start();
			}, asyncCheckDuration);
		});

		/********************
		   Option: Display   
		********************/

		QUnit.asyncTest("Option: Display", function() {
			var testDisplayBlock = "block",
				testDisplayNone = "none",
				testDisplayBlank = "";

			var $target1 = getTarget();
			/* Async checks are used since the display property is set inside processCallsTick(). */
			Velocity($target1, defaultProperties, { display: testDisplayBlock });
			setTimeout(function() {
				equal(Velocity.CSS.getPropertyValue($target1, "display"), testDisplayBlock, "Display:'block' was set immediately.");
			}, asyncCheckDuration);

			var $target2 = getTarget();
			Velocity($target2, defaultProperties, { display: testDisplayNone });
			setTimeout(function() {
				notEqual(Velocity.CSS.getPropertyValue($target2, "display"), 0, "Display:'none' was not set immediately.");
			}, asyncCheckDuration);
			setTimeout(function() {
				equal(Velocity.CSS.getPropertyValue($target2, "display"), 0, "Display:'none' was set upon completion.");
			}, completeCheckDuration);

			var $target3 = getTarget();
			Velocity($target3, defaultProperties, { display: testDisplayBlank });
			setTimeout(function() {
				equal(Velocity.CSS.getPropertyValue($target3, "display"), "block", "Display:'' was set immediately.");
				
				start();
			}, completeCheckDuration);
		});

		/***********************
		   Option: Visibility   
		***********************/

		QUnit.asyncTest("Option: Visibility", function() {
			var testVisibilityBlock = "visible",
				testVisibilityNone = "hidden",
				testVisibilityBlank = "";

			var $target1 = getTarget();
			/* Async checks are used since the visibility property is set inside processCallsTick(). */
			Velocity($target1, defaultProperties, { visibility: testVisibilityBlock });
			setTimeout(function() {
				equal(Velocity.CSS.getPropertyValue($target1, "visibility"), testVisibilityBlock, "visibility:'visible' was set immediately.");
			}, asyncCheckDuration);

			var $target2 = getTarget();
			Velocity($target2, defaultProperties, { visibility: testVisibilityNone });
			setTimeout(function() {
				notEqual(Velocity.CSS.getPropertyValue($target2, "visibility"), 0, "visibility:'hidden' was not set immediately.");
			}, asyncCheckDuration);
			setTimeout(function() {
				equal(Velocity.CSS.getPropertyValue($target2, "visibility"), "hidden", "visibility:'hidden' was set upon completion.");
			}, completeCheckDuration);

			var $target3 = getTarget();
			Velocity($target3, defaultProperties, { display: testVisibilityBlank });
			setTimeout(function() {
				equal(/visible|inherit/.test(Velocity.CSS.getPropertyValue($target3, "visibility")), true, "visibility:'' was set immediately.");
				
				start();
			}, completeCheckDuration);
		});

		/******************
		   Option: Loop
		******************/

		QUnit.asyncTest("Option: Loop", function() {
			expect(6);

			var testOptions = { delay: 500, easing: "spring" };

			var $target1 = getTarget();
			Velocity($target1, defaultProperties, { loop: 2, delay: testOptions.delay, easing: testOptions.easing });

			/* We expect 1 delay followed by 1 call for a total of 4 cycles, which equates to 8 queue items. */
			equal(Velocity.Utilities.queue($target1).length, 8, "Loop call produced 'reverse' calls.");

			var $target2 = getTarget();
			Velocity($target2, defaultProperties, { loop: true, delay: testOptions.delay, easing: testOptions.easing });
			setTimeout(function() {
				equal(Data($target1, pluginName).opts.delay, testOptions.delay, "Delay option was passed into second loop call (jQuery object).");
				equal(Data($target1, pluginName).opts.easing, testOptions.easing, "Easing option was passed into second loop call (jQuery object).");

				equal(Data($target2, pluginName).opts.delay, testOptions.delay, "Delay option was passed into second infinite loop call (jQuery object).");
				equal(Data($target2, pluginName).opts.easing, testOptions.easing, "Easing option was passed into second infinite loop call (jQuery object).");

				equal(Velocity.Utilities.queue($target2).length, 2, "Infinite loop is running.");

				start();
			}, completeCheckDuration + testOptions.delay);
		});

		/*******************
		   Option: Begin
		*******************/

		QUnit.asyncTest("Option: Begin", function() {
			expect(1);

			var $targetSet = [ getTarget(), getTarget() ];
			Velocity($targetSet, defaultProperties, { 
				duration: asyncCheckDuration,
				begin: function() {
					deepEqual(this, $targetSet, "Elements passed into callback.");

					start();
				}
			});
		});

		/*********************
		   Option: Complete
		*********************/

		QUnit.asyncTest("Option: Complete", function() {
			expect(1);
			
			var $targetSet = [ getTarget(), getTarget() ];
			Velocity($targetSet, defaultProperties, { 
				duration: asyncCheckDuration,
				complete: function() {
					deepEqual(this, $targetSet, "Elements passed into callback.");

					start();
				}
			});
		});

		/*********************
		   Option: Progress
		*********************/

		QUnit.asyncTest("Option: Progress", function() {
			expect(3);

			var alreadyCalled = false;

			var $target = getTarget();
			Velocity($target, defaultProperties, { 
				duration: asyncCheckDuration,
				progress: function(elements, percentComplete, msRemaining) {
					if (!alreadyCalled) {
						deepEqual(this, [ $target ], "Elements passed into progress.");
						equal(percentComplete >= 0 && percentComplete <= 1, true, "percentComplete passed into progress.");
						equal(msRemaining > asyncCheckDuration - 50, true, "msRemaining passed into progress.");

						alreadyCalled = true;
						start();
					}
				}
			});
		});

		/*********************
		   Command: Reverse
		*********************/

		QUnit.asyncTest("Command: Reverse", function() {
			expect(5);

			var testEasing = "spring";

			var $target = getTarget();
			/* Ensure an error isn't thrown when there's no previous animation to reverse to. */
			Velocity($target, "reverse");
			Velocity($target, { opacity: defaultProperties.opacity, width: defaultProperties.width }, { easing: testEasing });
			Velocity($target, "reverse", function() {
				equal(parseFloat(Velocity.CSS.getPropertyValue($target, "opacity")), defaultStyles.opacity, "Reversed to initial property #1.");
				equal(parseFloat(Velocity.CSS.getPropertyValue($target, "width")), defaultStyles.width, "Reversed to initial property #2.");
			});
			/* Check chained reverses. */
			Velocity($target, "reverse", function() {
				equal(parseFloat(Velocity.CSS.getPropertyValue($target, "opacity")), defaultProperties.opacity, "Reversed to reversed property #1.");
				equal(parseFloat(Velocity.CSS.getPropertyValue($target, "width")), defaultProperties.width, "Reversed to reversed property #2.");

				/* Ensure the options were passed through until the end. */
				equal(Data($target, pluginName).opts.easing, testEasing, "Options object passed through.");

				start();
			});
		});

		/******************
		   Command: Stop
		******************/

		QUnit.asyncTest("Command: Stop", function() {
			expect(4);

			var $target1 = getTarget();
			/* Ensure an error isn't thrown when "stop" is called on a $target that isn't animating. */
			Velocity($target1, "stop");
			Velocity($target1, defaultProperties, defaultOptions);
			Velocity($target1, { top: 0 }, defaultOptions);
			Velocity($target1, { width: 0 }, defaultOptions);
			Velocity($target1, "stop", true);

			/* Ensure "stop" has removed all queued animations. */
			/* We're using the element's queue length as a proxy. 0 and 1 both mean that the element's queue has been cleared -- a length of 1 just indicates that the animation is in progress. */
			setTimeout(function() {
				equal(Velocity.Utilities.queue($target1).length <= 1, true, "Queue cleared.");
			}, 1);

			var $target2 = getTarget();
			Velocity($target2, { opacity: 0 }, Velocity.Utilities.extend({}, defaultOptions, { delay: 1000 }));
			Velocity($target2, { width: 0 }, defaultOptions);
			Velocity($target2, "stop");

			var $target3 = getTarget();
			Velocity($target3, { opacity: 0 }, Velocity.Utilities.extend({}, defaultOptions, { delay: 1000 }));
			Velocity($target3, { width: 0 }, defaultOptions);
			Velocity($target3, { width: 100 }, defaultOptions);
			Velocity($target3, "stop", true);

			setTimeout(function() {
				equal(Data($target2, pluginName).tweensContainer.opacity, undefined, "Active call stopped.");
				notEqual(Data($target2, pluginName).tweensContainer.width, undefined, "Next queue item started.");

				equal(Velocity.Utilities.queue($target3, "").length, 0, "Full queue array cleared.");

				start();
			}, asyncCheckDuration);
		});

		/******************
		   Command: Finish
		******************/

		QUnit.asyncTest("Command: Finish / FinishAll", function() {
			expect(9);

			var $target1 = getTarget();
			/* Ensure an error isn't thrown when "finish" is called on a $target that isn't animating. */
			Velocity($target1, "finish");

			/* Animate to defaultProperties, and then "finish" to jump to the end of it. */
			Velocity($target1, defaultProperties, Velocity.Utilities.extend({}, defaultOptions, { delay: 1000}));
			Velocity($target1, "finish");

			setTimeout(function() {
				/* Ensure "finish" has removed all queued animations. */
				/* We're using the element's queue length as a proxy. 0 and 1 both mean that the element's queue has been cleared -- a length of 1 just indicates that the animation is in progress. */
				equal(Velocity.Utilities.queue($target1).length <= 1, true, "Queue cleared.");

				/* End result of the animation should be applied */
				equal(parseFloat(Velocity.CSS.getPropertyValue($target1, "width")), defaultProperties.width, "Standard end value #1 was set.");
				equal(parseFloat(Velocity.CSS.getPropertyValue($target1, "opacity")), defaultProperties.opacity, "Standard end value #2 was set.");
			}, asyncCheckDuration);

			var $target2 = getTarget();
			Velocity($target2, { opacity: 0 }, Velocity.Utilities.extend({}, defaultOptions, { delay: 1000 }));
			Velocity($target2, { width: 0 }, defaultOptions);
			Velocity($target2, "finish");

			var $target3 = getTarget();
			Velocity($target3, { opacity: 0, width: 50 }, Velocity.Utilities.extend({}, defaultOptions, { delay: 1000 }));
			Velocity($target3, { width: 0 }, defaultOptions);
			Velocity($target3, { width: 100 }, defaultOptions);
			Velocity($target3, "finish", true);

			var $target4 = getTarget();
			Velocity($target4, { opacity: 0, width: 50 }, Velocity.Utilities.extend({}, defaultOptions, { delay: 1000 }));
			Velocity($target4, { width: 0 }, defaultOptions);
			Velocity($target4, { width: 100 }, defaultOptions);
			Velocity($target4, "finishAll", true);

			setTimeout(function() {
				equal(Data($target2, pluginName).tweensContainer.opacity, undefined, "Active call stopped.");
				notEqual(Data($target2, pluginName).tweensContainer.width, undefined, "Next queue item started.");

				equal(Velocity.Utilities.queue($target3, "").length, 0, "Full queue array cleared.");
				equal(parseFloat(Velocity.CSS.getPropertyValue($target3, "width")), 50, "Just the first call's width was applied.");

				equal(Velocity.Utilities.queue($target4, "").length, 0, "Full queue array cleared.");
				equal(parseFloat(Velocity.CSS.getPropertyValue($target4, "width")), 100, "The last call's width was applied.");

				start();
			}, asyncCheckDuration);
		});

		/***********************
		   Feature: Redirects
		***********************/

		QUnit.asyncTest("Feature: Redirects", function() {
			var $target1 = getTarget(),
				$target2 = getTarget(),
				redirectOptions = { duration: 1500 };

			(window.jQuery || window.Zepto || window).Velocity.Redirects.test = function (element, options, elementIndex, elementsLength) {
				if (elementIndex === 0) {
					deepEqual(element, $target1, "Element passed through #1.");
					deepEqual(options, redirectOptions, "Options object passed through #1.");
					equal(elementIndex, 0, "Element index passed through #1.");
					equal(elementsLength, 2, "Elements length passed through #1.");
				} else if (elementIndex === 1) {
					deepEqual(element, $target2, "Element passed through #2.");
					deepEqual(options, redirectOptions, "Options object passed through #2.");
					equal(elementIndex, 1, "Element index passed through #2.");
					equal(elementsLength, 2, "Elements length passed through #2.");

					start();
				}
			}

			Velocity([ $target1, $target2 ], "test", redirectOptions);
		});

		/************************
		   Feature: animating
		************************/

		QUnit.asyncTest("Feature: 'velocity-animating' Class", function() {
			var $target1 = getTarget();

			Velocity($target1, defaultProperties, function(element) { 
					equal(/velocity-animating/.test($target1.className), false, "Class removed.");
					start();
				}
			);

			setTimeout(function() {
				equal(/velocity-animating/.test($target1.className), true, "Class added."); 
			}, asyncCheckDuration);
		});

		/***********************
		   Feature: Promises
		***********************/

		QUnit.asyncTest("Feature: Promises", function() {
			expect(5);

			var $target1 = getTarget();

			Velocity($target1, defaultProperties, 10000).then(function(elements) {
				deepEqual(elements, [ $target1 ], "Active call fulfilled.");
			});

			Velocity($target1, defaultProperties, 10000).then(function(elements) {
				deepEqual(elements, [ $target1 ], "Queued call fulfilled.");
			});

			Velocity($target1, "stop", true).then(function(elements) {
				deepEqual(elements, [ $target1 ], "Stop call fulfilled.");
			});	

			var $target2 = getTarget(),
				$target3 = getTarget();

			Velocity([ $target2, $target3 ], "fake", defaultOptions)["catch"](function(error) {
				equal(error instanceof Error, true, "Invalid command caused promise rejection.");
			});

			Velocity([ $target2, $target3 ], defaultProperties, defaultOptions).then(function(elements) {
				deepEqual(elements, [ $target2, $target3 ], "Elements passed back into resolved promise.");

				start();
			});		
		});

		/*****************************
		   Feature: Value Functions    
		*****************************/

		QUnit.test("Feature: Value Functions", function() {
			var testWidth = 10;

			var $target1 = getTarget(),
				$target2 = getTarget();
			Velocity([ $target1, $target2 ], { width: function (i, total) { return (i + 1)/total * testWidth; } });

			equal(Data($target1, pluginName).tweensContainer.width.endValue, parseFloat(testWidth) / 2, "Function value #1 passed to tween.");
			equal(Data($target2, pluginName).tweensContainer.width.endValue, parseFloat(testWidth), "Function value #2 passed to tween.");
		});

		/***************************
		   Feature: Forcefeeding    
		***************************/

		QUnit.test("Feature: Forcefeeding", function() {
			/* Note: Start values are always converted into pixels. W test the conversion ratio we already know to avoid additional work. */
			var testStartWidth = "1rem", testStartWidthToPx = "16px",
				testStartHeight = "10px";

			var $target = getTarget();
			Velocity($target, { width: [ 100, "linear", testStartWidth ], height: [ 100, testStartHeight ], opacity: [ defaultProperties.opacity, "easeInQuad" ]});

			equal(Data($target, pluginName).tweensContainer.width.startValue, parseFloat(testStartWidthToPx), "Forcefed value #1 passed to tween.");
			equal(Data($target, pluginName).tweensContainer.height.startValue, parseFloat(testStartHeight), "Forcefed value #2 passed to tween.");
			equal(Data($target, pluginName).tweensContainer.opacity.startValue, defaultStyles.opacity, "Easing was misinterpreted as forcefed value.");
		});

		/*********************************
		   Feature: Colors (Shorthands)   
		*********************************/

		QUnit.test("Feature: Colors (Shorthands)", function() {
			var $target = getTarget();
			Velocity($target, { borderColor: "#7871c2", color: [ "#297dad", "spring", "#5ead29" ] });

			equal(Data($target, pluginName).tweensContainer.borderColorRed.endValue, 120, "Hex #1a component.");
			equal(Data($target, pluginName).tweensContainer.borderColorGreen.endValue, 113, "Hex #1b component.");
			equal(Data($target, pluginName).tweensContainer.borderColorBlue.endValue, 194, "Hex #1c component.");
			equal(Data($target, pluginName).tweensContainer.colorRed.easing, "spring", "Per-property easing.");
			equal(Data($target, pluginName).tweensContainer.colorRed.startValue, 94, "Forcefed hex #2a component.");
			equal(Data($target, pluginName).tweensContainer.colorGreen.startValue, 173, "Forcefed hex #2b component.");
			equal(Data($target, pluginName).tweensContainer.colorBlue.startValue, 41, "Forcefed hex #2c component.");
			equal(Data($target, pluginName).tweensContainer.colorRed.endValue, 41, "Hex #3a component.");
			equal(Data($target, pluginName).tweensContainer.colorGreen.endValue, 125, "Hex #3b component.");
			equal(Data($target, pluginName).tweensContainer.colorBlue.endValue, 173, "Hex #3c component.");
		});

		/**********************************
		   Packaged Effect: slideUp/Down
		**********************************/

		QUnit.asyncTest("Packaged Effect: slideUp/Down", function() {
			var $target1 = getTarget(),
				$target2 = getTarget();

			var initialStyles = {
				display: "none",
				paddingTop: "123px"
			};

			$target1.style.display = initialStyles.display;
			$target1.style.paddingTop = initialStyles.paddingTop;

			Velocity($target1, "slideDown", 
				{ 
					begin: function(elements) {
						deepEqual(elements, [ $target1 ], "slideDown: Begin callback returned.");
					},
					complete: function(elements) {
						deepEqual(elements, [ $target1 ], "slideDown: Complete callback returned.");
						equal(Velocity.CSS.getPropertyValue($target1, "display"), Velocity.CSS.Values.getDisplayType($target1), "slideDown: display set to default.");
						notEqual(Velocity.CSS.getPropertyValue($target1, "height"), 0, "slideDown: height set.");
						equal(Velocity.CSS.getPropertyValue($target1, "paddingTop"), initialStyles.paddingTop, "slideDown: paddingTop set.");
					}
				}
			).then(function(elements) {
				deepEqual(elements, [ $target1 ], "slideDown: Promise fulfilled.");
			});

			Velocity($target2, "slideUp", 
				{ 
					begin: function(elements) {
						deepEqual(elements, [ $target2 ], "slideUp: Begin callback returned.");
					},
					complete: function(elements) {
						deepEqual(elements, [ $target2 ], "slideUp: Complete callback returned.");
						equal(Velocity.CSS.getPropertyValue($target2, "display"), 0, "slideUp: display set to none.");
						notEqual(Velocity.CSS.getPropertyValue($target2, "height"), 0, "slideUp: height reset.");
						equal(Velocity.CSS.getPropertyValue($target1, "paddingTop"), initialStyles.paddingTop, "slideUp: paddingTop reset.");
					}
				}
			).then(function(elements) {
				deepEqual(elements, [ $target2 ], "slideUp: Promise fulfilled.");

				start();
			});
		});

		/***********************
		   UI Pack: Callbacks
		***********************/

		QUnit.asyncTest("UI Pack: Callbacks", function() {
			expect(3);

			var $targets = [ getTarget(), getTarget() ];

			Velocity($targets, "transition.bounceIn", 
				{ 
					begin: function(elements) {
						deepEqual(elements, $targets, "Begin callback returned.");
					},
					complete: function(elements) {
						deepEqual(elements, $targets, "Complete callback returned.");
					}
				}
			).then(function(elements) {
				deepEqual(elements, $targets, "Promise fulfilled.");

				start();
			});
		});

		/*********************
		   UI Pack: In/Out
		*********************/

		QUnit.asyncTest("UI Pack: In/Out", function() {
			expect(8);

			var $target1 = getTarget();
			Velocity($target1, "transition.bounceIn", defaultOptions.duration);

			var $target2 = getTarget();
			Velocity($target2, "transition.bounceIn", { duration: defaultOptions.duration, display: "inline" });

			var $target3 = getTarget();
			Velocity($target3, "transition.bounceOut", defaultOptions.duration);

			var $target4 = getTarget();
			Velocity($target4, "transition.bounceOut", { duration: defaultOptions.duration, display: null });

			var $target5 = getTarget();
			$target5.style.visibility = "hidden";
			Velocity($target5, "transition.bounceIn", { duration: defaultOptions.duration, visibility: "visible" });

			var $target6 = getTarget();
			$target6.style.visibility = "visible";
			Velocity($target6, "transition.bounceOut", { duration: defaultOptions.duration, visibility: "hidden" });

			setTimeout(function() {
				notEqual(Velocity.CSS.getPropertyValue($target3, "display"), 0, "Out: display not prematurely set to none.");
				notEqual(Velocity.CSS.getPropertyValue($target6, "visibility"), "hidden", "Out: visibility not prematurely set to hidden.");
			}, asyncCheckDuration)

			setTimeout(function() {
				equal(Velocity.CSS.getPropertyValue($target1, "display"), Velocity.CSS.Values.getDisplayType($target1), "In: display set to default.");
				equal(Velocity.CSS.getPropertyValue($target2, "display"), "inline", "In: Custom inline value set.");

				equal(Velocity.CSS.getPropertyValue($target3, "display"), 0, "Out: display set to none.");
				equal(Velocity.CSS.getPropertyValue($target4, "display"), Velocity.CSS.Values.getDisplayType($target3), "Out: No display value set.");

				equal(Velocity.CSS.getPropertyValue($target5, "visibility"), "visible", "In: visibility set to visible.");
				equal(Velocity.CSS.getPropertyValue($target6, "visibility"), "hidden", "Out: visibility set to hidden.");

				start();
			}, completeCheckDuration)
		});

		/**************************
		   UI Pack: Call Options
		**************************/

		QUnit.asyncTest("UI Pack: Call Options", function() {
			expect(7);

			var UICallOptions1 = {
					delay: 123,
					duration: defaultOptions.duration,
					loop: true, // Should get ignored
					easing: "spring" // Should get ignored
				};

			var $target1 = getTarget();
			Velocity($target1, "transition.slideLeftIn", UICallOptions1);

			setTimeout(function() {
				// Note: We can do this because transition.slideLeftIn is composed of a single call.
				equal(Data($target1, pluginName).opts.delay, UICallOptions1.delay, "Whitelisted option passed in.");
				notEqual(Data($target1, pluginName).opts.loop, UICallOptions1.loop, "Non-whitelisted option not passed in #1a.");
				notEqual(Data($target1, pluginName).opts.easing, UICallOptions1.easing, "Non-whitelisted option not passed in #1a.");
				equal(!/velocity-animating/.test(Data($target1, pluginName).className), true, "Duration option passed in.");
			}, completeCheckDuration);

			var UICallOptions2 = {
					stagger: 100,
					duration: defaultOptions.duration,
					backwards: true
				};

			var $targets = [ getTarget(), getTarget(), getTarget() ];
			Velocity($targets, "transition.slideLeftIn", UICallOptions2);

			setTimeout(function() {
				equal(Data($targets[0], pluginName).opts.delay, UICallOptions2.stagger * 2, "Backwards stagger delay passed in #1a.");
				equal(Data($targets[1], pluginName).opts.delay, UICallOptions2.stagger * 1, "Backwards stagger delay passed in #1b.");
				equal(Data($targets[2], pluginName).opts.delay, UICallOptions2.stagger * 0, "Backwards stagger delay passed in #1c.");

				start();
			}, completeCheckDuration);
		});

		/****************************
		   UI Pack: RegisterEffect
		****************************/

		QUnit.asyncTest("UI Pack: RegisterEffect", function() {
			expect(2);

			var effectDefaultDuration = 800;
			Velocity.RegisterUI("callout.twirl", {
			    defaultDuration: effectDefaultDuration,
			    calls: [ 
					[ { rotateZ: 1080 }, 0.50 ],
					[ { scaleX: 0.5 }, 0.25, { easing: "spring" } ],
					[ { scaleX: 1 }, 0.25, { easing: "spring" } ]
			    ]
			});

			var $target1 = getTarget();
			Velocity($target1, "callout.twirl");

			setTimeout(function() {
				equal(parseFloat(Velocity.CSS.getPropertyValue($target1, "rotateZ")), 1080, "First call's property animated.");
				equal(parseFloat(Velocity.CSS.getPropertyValue($target1, "scaleX")), 1, "Last call's property animated.");

				start();
			}, effectDefaultDuration * 1.50);
		});

		/*************************
		   UI Pack: RunSequence
		*************************/

		QUnit.asyncTest("UI Pack: RunSequence", function() {
			expect(3);

			var $target1 = getTarget(),
				$target2 = getTarget(),
				$target3 = getTarget();

			var mySequence = [
					{ elements: $target1, properties: { opacity: defaultProperties.opacity } },
					{ elements: $target2, properties: { height: defaultProperties.height } },
					{ elements: $target3, properties: { width: defaultProperties.width }, options: { 
							delay: 100,
							sequenceQueue: false,
							complete: function() {
								equal(parseFloat(Velocity.CSS.getPropertyValue($target1, "opacity")), defaultProperties.opacity, "First call's property animated.");
								equal(parseFloat(Velocity.CSS.getPropertyValue($target2, "height")), defaultProperties.height, "Second call's property animated.");
								equal(parseFloat(Velocity.CSS.getPropertyValue($target3, "width")), defaultProperties.width, "Last call's property animated.");

								start();
							}
						}
					}
				];

			Velocity.RunSequence(mySequence);
		});

		/*********************
		   Command: Scroll
		*********************/

		if ($) {
			/* Window scrolling. */
			QUnit.asyncTest("Command: Scroll (Window)", function() {
				var $details = $("#details"),
					$scrollTarget1 = $("<div>Scroll target #1. Should stop 50 pixels above this point.</div>"),
					$scrollTarget2 = $("<div>Scroll target #2. Should stop 50 pixels before this point.</div>"),
					scrollOffset = -50;

				$scrollTarget1
					.css({ position: "relative", top: 3000, height: 100, paddingBottom: 10000 })
					.appendTo($("body"));

				$scrollTarget2
					.css({ position: "absolute", top: 100, left: 3000, width: 100, paddingRight: 15000 })
					.appendTo($("body"));

				$scrollTarget1
					.velocity("scroll", { duration: 500, offset: scrollOffset, complete: function() {
							equal(Math.abs(Velocity.State.scrollAnchor[Velocity.State.scrollPropertyTop] - ($scrollTarget1.offset().top + scrollOffset)) <= 100, true, "Page scrolled top with a scroll offset.");
						}
					})
					.velocity({ opacity: 0.5 }, function() {
					    $details
						    .velocity({ opacity: 0.5 }, 500)
						    .velocity("scroll", 500)
						    .velocity({ opacity: 1 }, 500, function() {
						    	//alert(Velocity.State.scrollAnchor[Velocity.State.scrollPropertyTop] + " " + ($details.offset().top + scrollOffset))
								equal(Math.abs(Velocity.State.scrollAnchor[Velocity.State.scrollPropertyTop] - ($details.offset().top + scrollOffset)) <= 100, true, "Page scroll top was chained.");

								//$scrollTarget1.remove();

								$scrollTarget2
									.velocity("scroll", { duration: 500, axis: "x", offset: scrollOffset, complete: function() {
											/* Phones can reposition the browser's scroll position by a 10 pixels or so, so we just check for a value that's within that range. */
											equal(Math.abs(Velocity.State.scrollAnchor[Velocity.State.scrollPropertyLeft] - ($scrollTarget2.offset().left + scrollOffset)) <= 100, true, "Page scrolled left with a scroll offset.");
										}
									})
									.velocity({ opacity: 0.5 }, function() {
									    $details
										    .velocity({ opacity: 0.5 }, 500)
										    .velocity("scroll", { duration: 500, axis: "x" })
										    .velocity({ opacity: 1 }, 500, function() {
												equal(Math.abs(Velocity.State.scrollAnchor[Velocity.State.scrollPropertyLeft] - ($details.offset().left + scrollOffset)) <= 100, true, "Page scroll left was chained.");
												start();
										    });
									});
						    });
					});
			});

			/* Element scrolling. */
			QUnit.asyncTest("Command: Scroll (Element)", function() {
				expect(2);

				var $scrollTarget1 = $("\
					<div id='scroller'>\
						aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
						aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
						aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
						aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
						aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
						<div id='scrollerChild1'>\
							Stop #1\
							bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
							bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
							bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
							bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
							bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
						</div>\
						cccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
						cccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
						cccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
						cccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
						cccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
						<div id='scrollerChild2'>\
							Stop #2\
							dddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
							dddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
							dddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
							dddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
							dddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
						</div>\
						eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
						eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
						eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
						eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
						eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
					</div>\
				");

				$scrollTarget1
					.css({ position: "absolute", backgroundColor: "white", top: 100, left: "50%", width: 500, height: 100, overflowY: "scroll" })
					.appendTo($("body"));

				/* Test with a jQuery object container. */
				$("#scrollerChild1").velocity("scroll", { container: $("#scroller"), duration: 750, complete: function() {
						/* Test with a raw DOM element container. */
						$("#scrollerChild2").velocity("scroll", { container: $("#scroller")[0], duration: 750, complete: function() {
								/* This test is purely visual. */
								ok(true);

								$scrollTarget1.remove();

								var $scrollTarget2 = $("\
									<div id='scroller'>\
										<div id='scrollerChild1' style='float: left; width: 20%;'>\
											Stop #1\
											bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
											bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
											bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
											bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
											bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\
											cccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
											cccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
											cccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
											cccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
											cccccccccccccccccccccccccccccccccccccccccccccccccccccccc\
										</div>\
										<div id='scrollerChild2' style='float: right; width: 20%;'>\
											Stop #2\
											dddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
											dddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
											dddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
											dddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
											dddddddddddddddddddddddddddddddddddddddddddddddddddddddd\
											eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
											eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
											eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
											eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
											eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
										</div>\
									</div>\
								");

								$scrollTarget2
									.css({ position: "absolute", backgroundColor: "white", top: 100, left: "50%", width: 100, height: 500, overflowX: "scroll" })
									.appendTo($("body"));

								/* Test with a jQuery object container. */
								$("#scrollerChild2").velocity("scroll", { axis: "x", container: $("#scroller"), duration: 750, complete: function() {
										/* Test with a raw DOM element container. */
										$("#scrollerChild1").velocity("scroll", { axis: "x", container: $("#scroller")[0], duration: 750, complete: function() {
												/* This test is purely visual. */
												ok(true);

												$scrollTarget2.remove();

												start();
											}
										});
									}
								});
							}
						});
					}
				});
			});
		}
		</script>
	</body>
</html>